#ifndef PARTICLE_ENVIRON_H
#define PARTICLE_ENVIRON_H

#include "ParticleParams.h"
#include "ParticleEffect.h"
#include "CryPtrArray.h"

class CParticleEmitter;

//////////////////////////////////////////////////////////////////////////
// Physical environment management.
//
struct SPhysEnviron: Cry3DEngineBase
{
	// PhysArea caching.
	SExternForces			m_UniformForces;
	ETrinary					m_tUnderWater;
	uint32						m_nNonUniformFlags;		// EParticleEnviron flags of non-uniform areas found.	
	uint32						m_nNonCachedFlags;		// Env flags of areas requiring physics system access.

	// Nonuniform area support.
	struct SArea
	{
		_smart_ptr<IPhysicalEntity>
											m_pArea;
		volatile int*			m_pLock;							// Copy of lock for area.

		SExternForces			m_Forces;
		AABB							m_bbArea;							// Bounds of area, for quick checks.
		uint32						m_nFlags;
		bool							m_bOutdoorOnly;				// Force only for outdoor areas.

		// Area params, for simple evaluation.
		bool							m_bCacheForce;				// Quick cached force info.
		bool							m_bRadial;						// Forces are radial from center.
		Vec3							m_vCenter;						// Area position (for radial forces).
		Matrix33					m_matToLocal;					// Convert to unit sphere space.
		float							m_fInnerRadius;				// Inner fraction of sphere at max strength.

		SArea()
			{ ZeroStruct(*this); }
		void GetForce( Vec3 const& vPos, Vec3& vGravity, Vec3& vWind ) const;
		void GetForcePhys( Vec3 const& vPos, Vec3& vGravity, Vec3& vWind ) const;
		void GetMemoryUsage( ICrySizer* pSizer ) const
			{}

		void AddRef()
		{ 
			m_nRefCount++; 
		}
		void Release()
		{
			assert(m_nRefCount >= 0);
			if (--m_nRefCount == 0)
				delete this;
		}

	private:
		int								m_nRefCount;
	};


	SPhysEnviron()
	{ 
		Clear();
	}

	// Phys areas
	void Clear();
	void FreeMemory();

	// Query world phys areas.
	void GetWorldPhysAreas( uint32 nFlags, bool bNonUniformAreas );

	// Query subset of phys areas.
	void GetPhysAreas( SPhysEnviron const& envSource, AABB const& box, uint32 nFlags, const CParticleEmitter* pEmitterSkip, bool bIndoors, bool bNonUniformAreas, const Vec3 &vPosition  );

	int GetNumNonUniformAreas() const
	{
		return m_NonUniformAreas.size();
	}

	void LockAreas( uint32 nFlags, int iLock ) const
	{
#if !defined(__SPU__)
		for_array (i, m_NonUniformAreas)
		{
			if (m_NonUniformAreas[i].m_nFlags & nFlags)
				if (!m_NonUniformAreas[i].m_bCacheForce)
					CryInterlockedAdd(m_NonUniformAreas[i].m_pLock, iLock);
		}
#endif
	}

	SPU_NO_INLINE void CheckPhysAreas( Vec3 const& vPos, Vec3& vGravity, Vec3& vWind, uint32 nFlags ) const
	{
		vGravity = m_UniformForces.vAccel;
		vWind = m_UniformForces.vWind;
		if (m_nNonUniformFlags & nFlags)
			CheckNonUniformAreas( vPos, vGravity, vWind, nFlags );
	}

	SPU_NO_INLINE float DistanceFromWater( Plane& plWater, Vec3 const& vPos, float fSense, float fMaxDist = fHUGE ) const
	{
		plWater = m_UniformForces.plWater;
		float fDist = m_UniformForces.plWater.DistFromPlane(vPos) * fSense;
		if (fDist <= 0.f)
			return fDist;
		if (!(m_nNonUniformFlags & ENV_WATER))
			return fDist;
		return NonUniformDistanceFromWater( plWater, vPos, fSense, min(fDist,fMaxDist) );
	}

	// Phys collision
	static bool PhysicsCollision( ray_hit& hit, Vec3 const& vStart, Vec3 const& vEnd, float fRadius, uint32 nEnvFlags, IPhysicalEntity* pThisEntity = 0 );

	void GetMemoryUsage(ICrySizer* pSizer) const
	{
		pSizer->AddContainer(m_NonUniformAreas);
	}

protected:

	SmartPtrArray<SArea>	m_NonUniformAreas;

	void CheckNonUniformAreas( Vec3 const& vPos, Vec3& vGravity, Vec3& vWind, uint32 nFlags ) const;
	float NonUniformDistanceFromWater( Plane& plWater, Vec3 const& vPos, float fSense, float fMaxDist = fHUGE ) const;
};

//////////////////////////////////////////////////////////////////////////
// Vis area management.
//
struct SVisEnviron: Cry3DEngineBase
{
	SVisEnviron()
	{
		Clear();
	}

	void Clear()
	{
		memset(this, 0, sizeof(*this));
	}

	void Invalidate()
	{
		m_bValid = false;
	}	
	void Update( Vec3 const& vOrigin, AABB const& bbExtents );

	bool NeedVisAreaClip() const
	{
		return m_pClipVisArea != 0;
	}

	SPU_NO_INLINE bool ClipVisAreas( Sphere& sphere, Vec3 const& vNormal ) const
	{
		if (!m_pClipVisArea)
			return false;
		// Clip inside or outside specified area.
		return m_pClipVisArea->ClipToVisArea(m_pClipVisArea == m_pVisArea, sphere, vNormal);
	}

	bool OriginIndoors() const
	{
		return m_pVisArea != 0;
	}

	void OnVisAreaDeleted(IVisArea* pVisArea)
	{
		if (m_pVisArea == pVisArea || m_pClipVisArea == pVisArea)
			Clear();
	}

protected:
	bool							m_bValid;							// True if VisArea determination up to date.
	bool							m_bCrossesVisArea;
	IVisArea*					m_pVisArea;						// VisArea emitter is in, if needed and if any.
	IVisArea*					m_pClipVisArea;				// VisArea needed to clip against (-1 if all).
	void*							m_pVisNodeCache;
};

//////////////////////////////////////////////////////////////////////////
// GeomRef functions.

struct GeomRef;

void NormalizeEmitGeom( GeomRef& geom, EGeomType eAttachType );
void GetAABB( AABB& bb, QuatTS const& tLoc, GeomRef const& geom );
float GetExtent( GeomRef const& geom, EGeomForm eForm );
void GetRandomPos( PosNorm& ran, GeomRef const& geom, EGeomForm eForm, QuatTS const& tWorld );

#endif
